﻿
Imports System.Threading
Imports System.Data.SqlClient
Imports Microsoft.SqlServer.Management.Common
Imports Microsoft.SqlServer.Management.SqlParser
Imports Microsoft.SqlServer.Management.SqlParser.Parser
Imports Microsoft.SqlServer.Management.SqlParser.Binder
Imports Microsoft.SqlServer.Management.SmoMetadataProvider
Imports Microsoft.SqlServer.Management.SqlParser.Intellisense
Imports Microsoft.SqlServer.Management.SqlParser.MetadataProvider
Imports ICSharpCode.AvalonEdit.CodeCompletion
Imports System.Text.RegularExpressions

Public Class QueryEditor
    Private connection As SqlConnection
    Private serverConnection As ServerConnection
    Private metaProvider As SmoMetadataProvider
    Private binder As IBinder
    Private metadataDisplayInfoProvider As MetadataDisplayInfoProvider
    Private completionWindow As CompletionWindow

    Public ReadOnly Property QueryContents As String
        Get
            Return textEditor.Text
        End Get
    End Property

    Public Overloads Function ShowDialog(ByVal ConnectionString) As Boolean
        If Not String.IsNullOrEmpty(ConnectionString) Then
            connection = New SqlConnection(ConnectionString)
            serverConnection = New ServerConnection(connection)
            metaProvider = SmoMetadataProvider.CreateConnectedProvider(serverConnection)
            binder = BinderProvider.CreateBinder(metaProvider)
            metadataDisplayInfoProvider = New MetadataDisplayInfoProvider()
            completionWindow = New CompletionWindow(textEditor.TextArea)

            AddHandler textEditor.TextArea.TextEntered, New TextCompositionEventHandler(AddressOf textEditor_AutoComplete)

        End If

        Return MyBase.ShowDialog()
    End Function



    Private Sub textEditor_AutoComplete(ByVal sender As Object, ByVal e As TextCompositionEventArgs)

        Suggest(textEditor.TextArea.Caret.Position.Line, textEditor.TextArea.Caret.Position.Column, textEditor.Text, Sub(s As List(Of Declaration), line As Integer, column As Integer)
                                                                                                                         If line = textEditor.TextArea.Caret.Position.Line AndAlso column = textEditor.TextArea.Caret.Position.Column AndAlso s.Count > 0 Then
                                                                                                                             If Not IsNothing(completionWindow) Then completionWindow = Nothing
                                                                                                                             completionWindow = New ICSharpCode.AvalonEdit.CodeCompletion.CompletionWindow(textEditor.TextArea)
                                                                                                                             Dim items As IList(Of ICSharpCode.AvalonEdit.CodeCompletion.ICompletionData) = completionWindow.CompletionList.CompletionData

                                                                                                                             For Each d As Declaration In s
                                                                                                                                 items.Add(New CompletionItem(d))
                                                                                                                             Next
                                                                                                                             completionWindow.Show()
                                                                                                                             AddHandler completionWindow.Closed, Sub() completionWindow = Nothing
                                                                                                                         Else
                                                                                                                             If Not IsNothing(completionWindow) Then
                                                                                                                                 completionWindow.Close()
                                                                                                                             End If
                                                                                                                             completionWindow = Nothing
                                                                                                                         End If
                                                                                                                     End Sub)
    End Sub

    Public Async Sub Suggest(ByVal lineNumber As Integer, ByVal columnNumber As Integer, ByVal query As String, ByVal callBack As [Delegate])
        Dim suggestions As List(Of Declaration) = Await getSuggestionsAsync(lineNumber, columnNumber, query)
        callBack.DynamicInvoke(suggestions, lineNumber, columnNumber)
    End Sub

    Private Async Function getSuggestionsAsync(ByVal lineNumber As Integer, ByVal columnNumber As Integer, ByVal query As String) As Task(Of List(Of Declaration))
        Dim suggestionTask As Task(Of List(Of Declaration)) = Task.Factory.StartNew(Function()
                                                                                        Return getSuggestions(lineNumber, columnNumber, query)
                                                                                    End Function)

        Return Await suggestionTask
    End Function

    Private Function getSuggestions(ByVal lineNumber As Integer, ByVal columnNumber As Integer, ByVal query As String) As List(Of Declaration)
        Dim parseResult As ParseResult = Parser.Parse(query)
        Dim parseResults As New List(Of ParseResult)() From {parseResult}

        SyncLock Me
            Dim server = binder.Bind(parseResults, connection.Database, BindMode.Batch)
        End SyncLock

        Dim declarations As IList(Of Declaration) = Resolver.FindCompletions(parseResult, lineNumber, columnNumber, metadataDisplayInfoProvider)
        Dim hint As String = query.Split(vbCrLf)(lineNumber - 1).Substring(0, columnNumber - 1)
        Dim hintMatch As Match = Regex.Match(hint, ".+[\s\.]+(?<hint>[a-zA-Z0-9_]*)")
        Dim mustContain As String = String.Empty

        If hintMatch.Success Then
            If Not IsNothing(hintMatch.Groups("hint")) Then
                If hintMatch.Groups("hint").Success Then
                    mustContain = hintMatch.Groups("hint").Value
                End If
            End If

        End If

        Dim suggestions As List(Of Declaration) = (From d In declarations
                                              Where
                                              d.Type = DeclarationType.Database Or
                                              d.Type = DeclarationType.Table Or
                                              d.Type = DeclarationType.Column Or
                                              d.Type = DeclarationType.ForeignKeyColumn Or
                                              d.Type = DeclarationType.IndexedColumn Or
                                              d.Type = DeclarationType.BuiltInFunction Or
                                              d.Type = DeclarationType.StoredProcedure Or
                                              d.Type = DeclarationType.ScalarVariable
                                              Select d).ToList()
        If Not String.IsNullOrEmpty(mustContain) Then
            suggestions = (From d In suggestions Where d.Title.ToLower.StartsWith(mustContain.ToLower) Select d).ToList()
            Debug.Print("Must Contain: " + mustContain)
        End If


        Return suggestions

    End Function


    Private Sub QueryEditor_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
        Using stream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("SqlAppifier.sql.xhsd")
            Using reader = New System.Xml.XmlTextReader(stream)
                textEditor.SyntaxHighlighting = ICSharpCode.AvalonEdit.Highlighting.Xshd.HighlightingLoader.Load(reader, ICSharpCode.AvalonEdit.Highlighting.HighlightingManager.Instance)
            End Using
        End Using
    End Sub

    Private Sub CancelBtn_Click(sender As Object, e As RoutedEventArgs)
        Me.DialogResult = False
    End Sub

    Private Sub SaveBtn_Click(sender As Object, e As RoutedEventArgs)
        Me.DialogResult = True
    End Sub
End Class
